import { ApiMeta, Stability } from '@components/ApiMeta';
import WebpackLicense from '@components/WebpackLicense';

<WebpackLicense from="https://webpack.docschina.org/api/loaders/#the-loader-context" />

# Loader 上下文

Loader 上下文表示 loader 内部可用的属性，这些属性在 loader 中通过 `this` 属性进行访问。

## this.addContextDependency()

- **类型：**

```ts
function addContextDependency(directory: string): void;
```

添加目录作为 loader 结果的依赖，使目录中文件的任何变化可以被监听到。

例如，添加 `src/static` 目录作为依赖，当目录中的文件发生变化时，会触发重新构建。

```js title="loader.js"
const path = require('node:path');

module.exports = function loader(source) {
  this.addContextDependency(path.resolve(this.rootContext, 'src/static'));
  return source;
};
```

## this.addDependency()

- **类型：**

```ts
function addDependency(file: string): void;
```

添加一个文件作为 loader 结果的依赖，使它们的任何变化可以被监听到。例如，`sass-loader`、`less-loader` 就使用了这个技巧，当导入的样式文件发生变化时就会重新编译。

```js title="loader.js"
const path = require('node:path');

module.exports = function loader(source) {
  this.addDependency(path.resolve(this.rootContext, 'src/styles/foo.scss'));
  return source;
};
```

## this.addMissingDependency()

- **类型：**

```ts
function addMissingDependency(file: string): void;
```

添加一个当前不存在的文件作为 loader 结果的依赖，使它们的创建和任何变化可以被监听到。例如，当该路径下新建了文件时，会触发重新构建。

```js title="loader.js"
const path = require('node:path');

module.exports = function loader(source) {
  this.addMissingDependency(
    path.resolve(this.rootContext, 'src/dynamic-file.json'),
  );
  return source;
};
```

## this.async()

- **类型：** `() => LoaderContextCallback`

告诉 Rspack 这个 loader 将会异步被调用。返回值为 [this.callback](#thiscallback)。

> 查看 [异步 loader](/api/loader-api/writing-loaders#异步-loader) 了解如何使用。

## this.cacheable()

- **类型：**

```ts
function cacheable(flag: boolean = true): void;
```

默认情况下，loader 的处理结果会被标记为可缓存。调用这个方法然后传入 `false`，可以关闭 loader 处理结果的缓存能力。

```js title="loader.js"
module.exports = function loader(source) {
  this.cacheable(false);
  return source;
};
```

## this.callback()

- **类型：**

```ts
function callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: Rspack.RawSourceMap,
  meta?: any,
): void;
```

将 Loader 处理后的结果告诉 Rspack。

第一个参数必须是 `Error` 或者 `null`，会标记当前模块为编译失败，第二个参数是一个 `string` 或者 `Buffer`，表示模块被该 Loader 处理后的文件内容，第三个参数是一个可以该 Loader 处理后的 source map，第四个参数会被 Rspack 忽略，可以是任何东西（例如一些元数据）。

> 查看 [同步 loader](/api/loader-api/writing-loaders#同步-loader) 了解如何使用。

:::warning
当这个函数被调用时，你应该返回 `undefined` 以避免 loader 结果的歧义。

传递给 `this.callback` 的值会传递给下一个 loader。
`sourceMap` 和 `meta` 参数是可选的，如果没有传递，那么下一个 loader 将不会收到它们。
:::

## this.clearDependencies()

- **类型：**

```ts
function clearDependencies(): void;
```

移除 loader 结果的所有依赖。

## this.context

- **类型：** `string | null`

当前被处理的模块所在的目录路径，会随着每个被处理的模块的位置而变化。

例如，如果 loader 处理的是 `/project/src/components/Button.js`，那么 `this.context` 的值就是 `/project/src/components`。

```js title="loader.js"
module.exports = function loader(source) {
  console.log(this.context); // '/project/src/components'
  return source;
};
```

如果被处理的是模块不是来自文件系统，例如虚拟模块，那么 `this.context` 的值为 `null`。

## this.loaderIndex

- **类型：** `number`

当前 loader 在 loaders 数组中的索引。

## this.data

- **类型：** `unknown`

用于在 pitch 和 normal 阶段之间共享数据。

## this.dependency()

- **类型：**

```ts
function dependency(file: string): void;
```

[this.addDependency()](#thisadddependency) 的别名。

## this.emitError()

- **类型：**

```ts
function emitError(err: Error): void;
```

发出一个错误，与在 loader 中 `throw` 和 `this.callback(err)` 不同，它不会标记当前模块为编译失败，只会在 Rspack 的 Compilation 上添加一个错误，并在此次编译结束后显示在命令行中。

## this.emitWarning()

- **类型：**

```ts
function emitWarning(warning: Error): void;
```

发出一个警告。

## this.experiments.emitDiagnostic()

<ApiMeta stability={Stability.Experimental} />

- **类型：**

```ts
interface DiagnosticLocation {
  /** Text for highlighting the location */
  text?: string;
  /** 1-based line */
  line: number;
  /** 0-based column in bytes */
  column: number;
  /** Length in bytes */
  length: number;
}

interface Diagnostic {
  message: string;
  help?: string;
  sourceCode?: string;
  /**
   * 源代码的位置信息
   * 如果未提供 `sourceCode`，则位置信息将被省略
   */
  location?: DiagnosticLocation;
  /**
   * 可选展示的文件名
   * 如果设置，它将成为 stats 中的 `StatsError.file` 值
   */
  file?: string;
  severity: 'error' | 'warning';
}

function emitDiagnostic(diagnostic: Diagnostic): void;
```

格式化并输出一个诊断信息（错误或警告日志），支持显示模块路径、源代码片段和行列号。

::: info
与在 loader 中 `throw` 和 `this.callback(err)` 不同，它不会标记当前模块为编译失败，只会在 Rspack 的 Compilation 上添加一个错误，并在此次编译结束后显示在命令行中。
:::

- 基础示例：

当仅使用 `message` 和 `severity` 时，仅会打印基础的模块错误信息。

```js title="loader.js"
/** @type {import("@rspack/core").LoaderDefinition} */
module.exports = function () {
  this.experiments.emitDiagnostic({
    message: '`React` is not defined',
    severity: 'error',
  });
  this.experiments.emitDiagnostic({
    message: '`React` is not defined',
    severity: 'warning',
  });
  return '';
};
```

将会打印：

```
ERROR in (./loader.js!)
  × ModuleError: `React` is not defined

WARNING in (./loader.js!)
  ⚠ ModuleWarning: `React` is not defined
```

- 打印代码片段：

```js title="loader.js"
/** @type {import("@rspack/core").LoaderDefinition} */
module.exports = function () {
  this.experiments.emitDiagnostic({
    message: '`React` is not defined',
    severity: 'error',
    sourceCode: `<div></div>`,
    location: {
      line: 1,
      column: 1,
      length: 3,
    },
    file: './some-file.js',
  });
  return '';
};
```

将会打印：

```
ERROR in ./some-file.js
 ./file.js 1:1-4
  × ModuleError: `React` is not defined
   ╭────
 1 │ <div></div>
   ·  ───
   ╰────
```

其中 `./some-file.js` 为传入的 `file` 字段。

## this.emitFile()

- **类型：**

```ts
function emitFile(
  name: string,
  content: string | Buffer,
  sourceMap?: string,
  assetInfo?: AssetInfo,
): void;
```

输出一个新文件。这个方法允许你在 loader 执行过程中创建新的文件。

- 基础示例：

```js title="loader.js"
module.exports = function loader(source) {
  // 输出一个新文件，该文件将在产物目录中输出为 `foo.js`
  this.emitFile('foo.js', 'console.log("Hello, world!");');
  return source;
};
```

- 带有 asset info 的示例：

```js title="loader.js"
module.exports = function loader(source) {
  this.emitFile(
    'foo.js',
    'console.log("Hello, world!");',
    undefined, // no sourcemap
    {
      sourceFilename: this.resourcePath,
    },
  );

  return source;
};
```

## this.fs

- **类型：** `InputFileSystem`

访问 `compilation` 对象的 `inputFileSystem` 属性。

## this.getOptions()

- **类型：**

```ts
function getOptions(schema?: any): OptionsType;
```

获取 loader 的使用者传入的选项。

例如：

```js title="rspack.config.mjs"
export default {
  module: {
    rules: [
      {
        test: /\.txt$/,
        use: {
          loader: './my-loader.js',
          options: {
            foo: 'bar',
          },
        },
      },
    ],
  },
};
```

在 `my-loader.js` 中获取传入的选项：

```js title="my-loader.js"
module.exports = function myLoader(source) {
  const options = this.getOptions();
  console.log(options); // { foo: 'bar' }
  return source;
};
```

在 TypeScript 中，你可以通过 `LoaderContext` 的泛型来设置 options 的类型。

```ts title="my-loader.ts"
import type { LoaderContext } from '@rspack/core';

type MyLoaderOptions = {
  foo: string;
};

export default function myLoader(
  this: LoaderContext<MyLoaderOptions>,
  source: string,
) {
  const options = this.getOptions();
  console.log(options); // { foo: 'bar' }
  return source;
}
```

:::tip
参数 `schema` 是可选的，在 Rspack 中不会被使用。

为了提供最佳性能，Rspack 不会执行 schema 验证。如果你的 loader 需要 schema 验证，请自行调用 [scheme-utils](https://github.com/webpack/scheme-utils) 或其他 schema 验证库。
:::

## this.getResolve()

- **类型：**

```ts
function getResolve(options: ResolveOptions): resolve;
```

创建一个类似于 `this.resolve` 的解析函数。

## this.hot

- **类型：** `boolean`

是否启用了 HMR。

```js title="loader.js"
module.exports = function (source) {
  console.log(this.hot); // true if HMR is enabled
  return source;
};
```

## this.importModule()

- **类型：**

```ts
interface ImportModuleOptions {
  /**
   * 指定模块的 layer
   */
  layer?: string;
  /**
   * 构建模块时使用的 public path
   */
  publicPath?: PublicPath;
  /**
   * 目标 base uri
   */
  baseUri?: string;
}

// 传入回调函数
function importModule<T = any>(
  request: string,
  options: ImportModuleOptions | undefined,
  callback: (err?: null | Error, exports?: T) => any,
): void;
// 不传入回调函数时，返回 Promise
function importModule<T = any>(
  request: string,
  options?: ImportModuleOptions,
): Promise<T>;
```

在构建过程中编译和执行一个模块。这是 child compiler 的轻量级替代方案。

在没有提供回调函数时，`importModule` 会返回一个 Promise。

```js title="loader.js"
const path = require('node:path');

module.exports = async function loader(source) {
  const modulePath = path.resolve(this.rootContext, 'some-module.ts');
  const moduleExports = await this.importModule(modulePath, {
    // 可选参数
  });

  const result = someProcessing(source, moduleExports);
  return result;
};
```

或者你可以传递一个回调函数给它。

```js title="loader.js"
const path = require('node:path');

module.exports = function loader(source) {
  const callback = this.async();
  const modulePath = path.resolve(this.rootContext, 'some-module.ts');

  this.importModule(
    modulePath,
    // 可选参数
    undefined,
    (err, moduleExports) => {
      if (err) {
        return callback(err);
      }

      const result = someProcessing(source, moduleExports);
      callback(null, result);
    },
  );
};
```

## this.query

- **类型：** `string | OptionsType`

该值取决于 loader 的配置方式：

- 如果当前 loader 配置了一个选项对象，`this.query` 将指向这个对象。
- 如果当前 loader 没有选项，但是通过查询字符串调用，这将是一个以 `?` 开头的字符串。

## this.request

- **类型：** `string`

解析后的 module specifier 字符串。

例如，如果 `resource.js` 被 `loader1.js` 和 `loader2.js` 处理，那么 `this.request` 的值为 `/path/to/loader1.js!/path/to/loader2.js!/path/to/resource.js`。

## this.resolve()

- **类型：**

```ts
function resolve(
  context: string,
  request: string,
  callback: (err: Error | null, result: string) => void,
): void;
```

解析一个模块标识符。

- `context` 必须是一个目录的绝对路径。此目录用作解析的起始位置。
- `request` 是要被解析的模块标识符。
- `callback` 是一个处理解析路径的回调函数。

## this.mode

- **类型：** `Mode`

当 Rspack 运行时读取 [mode](/config/mode) 的值，可能的值为：`'production'`、`'development'`、`'none'`。

```js title="loader.js"
module.exports = function loader(source) {
  console.log(this.mode); // 'production' or other values
  return source;
};
```

## this.target

- **类型：** `Target`

当前编译的目标。对应 [`target`](/config/target) 配置项的值。

```js title="loader.js"
module.exports = function loader(source) {
  console.log(this.target); // 'web' or other values
  return source;
};
```

## this.utils

- **类型：**

```ts
type Utils = {
  absolutify: (context: string, request: string) => string;
  contextify: (context: string, request: string) => string;
  createHash: (algorithm?: string) => Hash;
};
```

访问以下 utils：

- `absolutify`: 返回一个新的 request 字符串，尽可能使用绝对路径。
- `contextify`: 返回一个新的 request 字符串，尽可能避免使用绝对路径。
- `createHash`: 基于提供的哈希函数返回一个新的 Hash 对象。

```js title="loader.js"
module.exports = function (content) {
  this.utils.contextify(
    this.context,
    this.utils.absolutify(this.context, './index.js'),
  );

  this.utils.absolutify(this.context, this.resourcePath);

  const mainHash = this.utils.createHash(
    this._compilation.outputOptions.hashFunction,
  );
  mainHash.update(content);
  mainHash.digest('hex');

  return content;
};
```

## this.resource

- **类型：** `string`

当前模块的路径字符串。比如 `'/abc/resource.js?query#hash'`。

```js title="loader.js"
module.exports = function loader(source) {
  console.log(this.resource); // '/abc/resource.js?query#hash'
  return source;
};
```

## this.resourcePath

- **类型：** `string`

当前模块的路径字符串，不包括 query 和 fragment 参数。比如 `'/abc/resource.js?query#hash'` 中的 `'/abc/resource.js'`。

```js title="loader.js"
module.exports = function loader(source) {
  console.log(this.resourcePath); // '/abc/resource.js'
  return source;
};
```

## this.resourceQuery

- **类型：** `string`

当前模块的路径字符串的 query 参数。比如 `'/abc/resource.js?query#hash'` 中的 `'?query'`。

```js title="loader.js"
module.exports = function loader(source) {
  console.log(this.resourceQuery); // '?query'
  return source;
};
```

## this.resourceFragment

- **类型：** `string`

当前模块的路径字符串的 fragment 参数。比如 `'/abc/resource.js?query#hash'` 中的 `'#hash'`。

```js title="loader.js"
module.exports = function loader(source) {
  console.log(this.resourceFragment); // '#hash'
  return source;
};
```

## this.rootContext

- **类型：** `string`

Rspack config 中通过 [context](/config/context) 配置的基础路径。

```js title="loader.js"
module.exports = function loader(source) {
  console.log(this.rootContext); // /path/to/project
  return source;
};
```

## this.sourceMap

- **类型：** `boolean`

是否应该生成 source map。

由于生成 source map 通常是一项耗费资源的任务，你应该检查是否需要 source map。

详见 [处理 Source Map](/api/loader-api/writing-loaders#处理-source-map)。

## this.getLogger()

- **类型：**

```ts
function getLogger(name?: string): Logger;
```

获取此次编译过程的 logger，可通过该 logger 记录消息。

## this.version

- **类型：** `number`

loader API 的版本号。当前为 2。

这对于提供向后兼容性很有用。基于版本号，你可以为 breaking changes 指定自定义逻辑或降级方案。

## 内部属性

:::warning
注意，使用 `this._compiler` 和 `this._compilation` 这些 Rspack 的内部属性会使你的 loader 失去独立性。

理想情况下，loader 应该只关注文件转换逻辑，对于给定的输入，输出是确定的，不依赖 Rspack 的内部状态。依赖这些内部对象会增加不可预测的行为，使测试和维护变得困难。

因此，建议仅在没有其他替代方案时才考虑使用这些属性。
:::

### this.\_compiler

- **类型：** `Compiler`

访问当前的 Rspack [Compiler](/api/javascript-api/compiler) 对象。

### this.\_compilation

- **类型：** `Compilation`

访问当前的 Rspack [Compilation](/api/javascript-api/compilation) 对象。
